﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Timers;
using BLL.Workflows;
using DO.Workflows;
using Timer=System.Timers.Timer;

namespace BLL.Workers
{
    public class JobDispatcherService
    {
        #region settings
        private readonly Mutex _SyncObj;
        private bool _IsActive;
        private readonly int RETRIEVAL_INTERVAL = 1000 * 60;   // 1 MIN
        private readonly int DISPATCHER_INTERVAL = 1000 * 5;  // 5 SEC
        private int JOB_POOL = 4;
        private readonly string EVENT_NAME = "JOB DISPATCHER";
        private readonly string EVENT_SORUCE = "DispatcherService";

        // email
        private readonly string _SMTPServerName;
        private readonly string _FromEmailAddress;
        private readonly string _UserName;
        private readonly string _Password;
        private readonly bool _RequireAuthentication;
        private readonly bool _EnableSSL;
        #endregion

        #region fields
        private EventLog _Log;
        private Timer _RetrievalTimer;
        private Timer _DispatcherTimer;
        private Dictionary<string, Type> _Components;
        private Dictionary<string, IWorker> _Workers;
        private readonly Dictionary<Guid, Job> _Jobs;
        private readonly Dictionary<Guid, Guid> _ComponentJobMaps;       // component id->job id
        private readonly Dictionary<Guid, IWorker> _JobWorkMaps;     // job id -> worker 
        private int _OngoingWorkerCount;
        #endregion

        #region init
        /// <summary></summary>
        public JobDispatcherService()
        {
            this._SyncObj = new Mutex(false, "JobQueue");
            if (this.ExistInConfigFile("EventLogSource"))
                this.EVENT_SORUCE = ConfigurationManager.AppSettings.Get("EventLogSource");
            if (this.ExistInConfigFile("EventLogName"))
                this.EVENT_NAME = ConfigurationManager.AppSettings.Get("EventLogName");
            if (this.ExistInConfigFile("JobRetrievalInterval"))
                this.RETRIEVAL_INTERVAL = int.Parse(ConfigurationManager.AppSettings.Get("JobRetrievalInterval"));
            if (this.ExistInConfigFile("JobDispatcherInterval"))
                this.DISPATCHER_INTERVAL = int.Parse(ConfigurationManager.AppSettings.Get("JobDispatcherInterval"));
            if (this.ExistInConfigFile("JobPool"))
                this.JOB_POOL = int.Parse(ConfigurationManager.AppSettings.Get("JobPool"));
            this._Jobs = new Dictionary<Guid, Job>();
            this._ComponentJobMaps = new Dictionary<Guid, Guid>();
            this._JobWorkMaps = new Dictionary<Guid, IWorker>();
            this._OngoingWorkerCount = 0;
            
            if (this.ExistInConfigFile("SmtpServer"))
                this._SMTPServerName = ConfigurationManager.AppSettings.Get("SmtpServer");
            if (this.ExistInConfigFile("FromEmailAddress"))
                this._FromEmailAddress = ConfigurationManager.AppSettings.Get("FromEmailAddress");
            if (this.ExistInConfigFile("EmailUserName"))
                this._UserName = ConfigurationManager.AppSettings.Get("EmailUserName");
            if (this.ExistInConfigFile("EmailPassword"))
                this._Password = ConfigurationManager.AppSettings.Get("EmailPassword");
            if (this.ExistInConfigFile("RequireAuthentication"))
                this._RequireAuthentication = bool.Parse(ConfigurationManager.AppSettings.Get("RequireAuthentication"));
            if (this.ExistInConfigFile("EnableSSL"))
                this._EnableSSL = bool.Parse(ConfigurationManager.AppSettings.Get("EnableSSL"));

            this.RegisterEventLog();
            this.RegisterComponentsAndWorkers();
        }

        private void RegisterEventLog()
        {
            if (!EventLog.SourceExists(this.EVENT_SORUCE))
            {
                EventLog.CreateEventSource(this.EVENT_SORUCE, this.EVENT_NAME);
            }
            this._Log = new EventLog(this.EVENT_NAME, ".", this.EVENT_SORUCE);
            this._Log.MaximumKilobytes = 4096;
            this._Log.ModifyOverflowPolicy(OverflowAction.OverwriteAsNeeded, 7);
        }

        private void RegisterComponentsAndWorkers()
        {
            _Components = new Dictionary<string, Type>();
            _Workers = new Dictionary<string, IWorker>();

            string dllFilePath = typeof(WorkflowComponentBase).Assembly.Location;
            DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(dllFilePath));
            while (dir.Name.ToLower() != "assembly" && dir.Parent != null)
            {
                dir = dir.Parent;
            }
            if (dir.Name.ToLower() == "assembly")
            {
                FileInfo[] dllInfos = dir.GetFiles("*.dll", SearchOption.AllDirectories);
                foreach (FileInfo dllInfo in dllInfos)
                {
                    try
                    {
                        Assembly assembly = Assembly.LoadFile(dllInfo.FullName);
                        Type[] types = assembly.GetTypes();
                        foreach (Type type in types)
                        {
                            if (type.IsSubclassOf(typeof(WorkflowComponentBase)) && !type.IsAbstract)
                                _Components.Add(type.Name, type);
                            if (type.GetInterface(typeof(IWorker).FullName) != null && !type.IsAbstract)
                            {
                                IWorker worker = (IWorker)Activator.CreateInstance(type);
                                _Workers.Add(worker.ComponentType.Name, worker);
                            }
                        }
                    }
                    catch
                    {
                    }
                }
            }
        }

        private bool ExistInConfigFile(string key)
        {
            if(ConfigurationManager.AppSettings[key]!=null)
                return true;
            else
                return false;
        }
        #endregion

        #region start
        /// <summary></summary>
        public void Start()
        {
            this._SyncObj.WaitOne();
            this._OngoingWorkerCount = 0;
            this._Jobs.Clear();
            this.StartRetrieval();
            this.StartDispatcher();
            this._IsActive = true;
            this._SyncObj.ReleaseMutex();
        }

        private void StartRetrieval()
        {
            if (this._RetrievalTimer != null)
                this._RetrievalTimer.Dispose();
            GC.Collect();
            GC.WaitForFullGCComplete();
            this._RetrievalTimer = new Timer(this.RETRIEVAL_INTERVAL);
            this._RetrievalTimer.Enabled = true;
            this._RetrievalTimer.Elapsed += new ElapsedEventHandler(RetrieveJobs);
            this._RetrievalTimer.Start();
        }

        private void StartDispatcher()
        {
            if (this.ExistInConfigFile("JobPool"))
                this.JOB_POOL = int.Parse(ConfigurationManager.AppSettings.Get("JobPool"));
            if (this._DispatcherTimer != null)
                this._DispatcherTimer.Dispose();
            this._DispatcherTimer = null;
            GC.Collect();
            GC.WaitForFullGCComplete();
            this._DispatcherTimer = new Timer(DISPATCHER_INTERVAL);
            this._DispatcherTimer.Enabled = true;
            this._DispatcherTimer.Elapsed += new ElapsedEventHandler(DispatchJobs);
            this._DispatcherTimer.Start();
        }

        /// <summary></summary>
        public bool IsActive
        {
            get
            {
                return this._IsActive;
            }
        }
        #endregion

        #region stop
        /// <summary></summary>
        public void Stop()
        {
            this._SyncObj.WaitOne();
            this._OngoingWorkerCount = 0;
            this._Jobs.Clear();
            this.StopRetrieval();
            this.StopDispatcher();
            this._IsActive = false;
            this._SyncObj.ReleaseMutex();
        }

        /// <summary></summary>
        public void StopRetrieval()
        {
            this._RetrievalTimer.Stop();
        }

        /// <summary></summary>
        public void StopDispatcher()
        {
            this._DispatcherTimer.Stop();
        }
        #endregion

        #region retrieval
        void RetrieveJobs(object sender, ElapsedEventArgs e)
        {
            this._SyncObj.WaitOne();
            this.StopRetrieval();
            try
            {
                this._SyncObj.WaitOne();
                List<string> componentTypeNames = new List<string>();
                foreach (string componentTypeName in ComponentsRegister.ComponentTypes.Keys)
                {
                    componentTypeNames.Add(componentTypeName);
                }
                List<Job> newJobList = WorkflowManager.CheckOutJobs(componentTypeNames);
                if (newJobList != null && newJobList.Count > 0)
                {
                    foreach(Job newJob in newJobList)
                    {
                        this._Jobs.Add(newJob.JobID, newJob);
                    }
                    this.LogInfo("Retrieved " + newJobList.Count + " jobs.");
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }

            this.StartRetrieval();
            this._SyncObj.ReleaseMutex();
        }
        #endregion

        #region dispatcher
        void DispatchJobs(object sender, ElapsedEventArgs e)
        {
            this._SyncObj.WaitOne();
            this.StopDispatcher();
            try
            {
                this._SyncObj.WaitOne();
                if (this._OngoingWorkerCount < this.JOB_POOL && this._Jobs.Count > 0)
                {
                    Guid[] jobIDs = new Guid[this._Jobs.Count];
                    this._Jobs.Keys.CopyTo(jobIDs, 0);
                    foreach (Guid jobID in jobIDs)
                    {
                        Job nextJob = this._Jobs[jobID];
                        if (nextJob.StartTime == null)
                        {
                            this.LogInfo("Trying to dispatch next queued jobs...");
                            IWorker worker = this.CreateWorkerAndSetInstruction(nextJob);
                            if (worker != null)
                            {
                                this._Jobs[nextJob.JobID] = nextJob;
                                if (this._JobWorkMaps.ContainsKey(nextJob.JobID))
                                {
                                    this._JobWorkMaps[nextJob.JobID] = worker;
                                }
                                else
                                {
                                    this._JobWorkMaps.Add(nextJob.JobID, worker);
                                }
                                worker.DoWorkAsync();

                                this._OngoingWorkerCount += 1;
                            }
                            
                            break;
                        }
                    }
                    Thread.Sleep(1000*20);
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }

            this.StartDispatcher();
            this._SyncObj.ReleaseMutex();

        }

        private IWorker CreateWorkerAndSetInstruction(Job job)
        {
            WorkflowComponentBase associatedComponent = WorkflowManager.GetComponent(job.ComponentID);
            IWorker workerInstance = _Workers[associatedComponent.GetType().Name];
            workerInstance.WorkerCancelledEvent += new WorkerCancelled(workerInstance_WorkerCancelledEvent);
            workerInstance.WorkerErredEvent += new WorkerHasError(workerInstance_WorkerErredEvent);
            workerInstance.WorkerFinishedEvent += new WorkerFinished(workerInstance_WorkerFinishedEvent);
            workerInstance.WorkerStartedEvent += new WorkerStarted(workerInstance_WorkerStartedEvent);
            workerInstance.WorkerStatusChangedEvent += new WorkerStatusChanged(workerInstance_WorkerStatusChangedEvent);

            InstructionBase jobInstruction = this.ComputeInstructionForJob(job);
            Run run = WorkflowManager.GetRunByJob(job.JobID);
            if (run.Variables != null && run.Variables.Count > 0)
            {
                jobInstruction.BindToGlobalVariableValues(run.Variables);
            }
            WorkflowDesign wfDesign = WorkflowManager.GetWorkflow(run.WorkflowID);
            //WorkflowComponentBase wfComponent = WorkflowDesignDALC.GetWorkflowComponent(job.ComponentID);
            workerInstance.SetInstruction(
                job.JobID, 
                jobInstruction.Serialize().ToString(), 
                wfDesign.WorkflowName, 
                associatedComponent.ComponentName);
            // workerInstance.DoWork();
            return workerInstance;
        }

        void workerInstance_WorkerStatusChangedEvent(Guid ticketID, Type workerType, InstructionBase instructionBase,
            string workflowName, string componentName, string message, int percent)
        {
            this.LogInfo(string.Format("{0} | {1}({2}) status: {3}", workflowName, componentName, workerType.Name,
                                       message));
        }

        void workerInstance_WorkerStartedEvent(Guid ticketID, IWorker workerReference, Type workerType, InstructionBase instructionBase)
        {
            try
            {
                this._SyncObj.WaitOne();
                lock (this._Jobs)
                {
                    Job job = null;
                    if (this._Jobs.ContainsKey(ticketID))
                    {
                        job = this._Jobs[ticketID];
                        job.StartTime = DateTime.Now;
                    }
                    else
                    {
                        job = WorkflowManager.GetJob(ticketID);
                        job.StartTime = DateTime.Now;
                    }
                    WorkflowManager.SaveJob(job);

                    this.LogInfo(string.Format("Worker {0} started", workerType.Name));

                    if (job.IsFirstJob)
                    {
                        Run run = WorkflowManager.GetRunByJob(job.JobID);
                        run.StartTime = job.StartTime;
                        WorkflowManager.SaveRun(run);
                    }
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }

        }

        void workerInstance_WorkerFinishedEvent(
            Guid ticketID, 
            Type workerType, 
            InstructionBase instructionBase, 
            ResultBase resultBase)
        {
            try
            {
                this._SyncObj.WaitOne();
                lock (this._Jobs)
                {
                    string result = string.Empty;
                    if (resultBase != null)
                    {
                        WorkflowManager.SaveJobOutput(resultBase);
                    }
                    this.LogInfo(string.Format("Worker {0} finished with result {1}", workerType.Name, result));

                    Job job = null;
                    if (this._Jobs.ContainsKey(ticketID))
                    {
                        job = this._Jobs[ticketID];
                    }
                    else
                    {
                        job = WorkflowManager.GetJob(ticketID);
                    }
                    job.FinishTime = DateTime.Now;
                    WorkflowManager.SaveJob(job);
                    this.RemoveJobFromMemory(job.JobID);


                    if (job.IsLastJob)
                    {
                        Run run = WorkflowManager.GetRunByJob(job.JobID);
                        run.FinishTime = DateTime.Now;
                        WorkflowManager.SaveRun(run);
                    }

                    this._OngoingWorkerCount -= 1;
                    this._OngoingWorkerCount = Math.Max(0, this._OngoingWorkerCount);
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }
        }

        void workerInstance_WorkerErredEvent(Guid ticketID, Type workerType, InstructionBase instructionBase, string error, bool isFatal)
        {
            try
            {
                this._SyncObj.WaitOne();
                lock (this._Jobs)
                {
                    Job job = null;
                    if (this._Jobs.ContainsKey(ticketID))
                    {
                        job = this._Jobs[ticketID];
                    }
                    else
                    {
                        job = WorkflowManager.GetJob(ticketID);
                    }
                    job.HasError = true;
                    job.FinishTime = null;
                    WorkflowManager.SaveJob(job);
                    this.RemoveJobFromMemory(job.JobID);

                    this.LogErrorMessage(string.Format("Worker {0} has exception: {1}", workerType.Name, error));

                    if (isFatal)
                    {
                        // decrement job queue
                        this._OngoingWorkerCount -= 1;
                        this._OngoingWorkerCount = Math.Max(0, this._OngoingWorkerCount);

                        // disable downstream jobs
                        List<Guid> jobIDsDisabled = WorkflowManager.DisableJob(job.JobID);
                        foreach (Guid disabledJobID in jobIDsDisabled)
                        {
                            this._Jobs.Remove(disabledJobID);
                            this._JobWorkMaps.Remove(disabledJobID);
                            foreach (Guid componentID in this._ComponentJobMaps.Keys)
                            {
                                Guid jobID = this._ComponentJobMaps[componentID];
                                if (jobID == disabledJobID)
                                {
                                    this._ComponentJobMaps.Remove(componentID);
                                }
                            }
                        }

                        // send email notification
                        WorkflowDesign wfDesign = WorkflowManager.GetWorkflowByRun(job.RunID);
                        WorkflowComponentBase wfComponent = wfDesign.Components[job.ComponentID];
                        if (!string.IsNullOrEmpty(wfDesign.FailureNotifyEmailAddress))
                        {
                            string[] toAddresses = wfDesign.FailureNotifyEmailAddress.Split(new char[] {',', ';'});
                            string subject = string.Format("Error in workflow [{0}]", wfDesign.WorkflowName);
                            string body = string.Format("<b>{0}</b><br/><hr>{1}", wfComponent.ComponentName, error);
                            this.SendNotification(this._SMTPServerName, this._EnableSSL, this._RequireAuthentication,
                                                  this._UserName, this._Password, this._FromEmailAddress, toAddresses,
                                                  null,
                                                  subject, body);
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }
        }

        void workerInstance_WorkerCancelledEvent(Guid ticketID, Type workerType, InstructionBase instructionBase)
        {
            try
            {
                this._SyncObj.WaitOne();
                lock (this._Jobs)
                {
                    this.LogInfo(string.Format("Worker {0} is cancelled by user.", workerType.Name));

                    if (this._Jobs.ContainsKey(ticketID))
                    {
                        Job job = this._Jobs[ticketID];
                        job.Cancelled = true;
                        job.FinishTime = null;
                        WorkflowManager.SaveJob(job);

                        this.RemoveJobFromMemory(job.JobID);

                        List<Guid> jobIDsDisabled = WorkflowManager.DisableJob(job.JobID);
                        foreach (Guid jobIDDisabled in jobIDsDisabled)
                        {
                            this._Jobs.Remove(jobIDDisabled);
                            this._JobWorkMaps.Remove(jobIDDisabled);
                            foreach (Guid componentID in this._ComponentJobMaps.Keys)
                            {
                                Guid jobID = this._ComponentJobMaps[componentID];
                                if (jobID == jobIDDisabled)
                                {
                                    this._ComponentJobMaps.Remove(componentID);
                                }
                            }
                        }
                    }
                    this._OngoingWorkerCount -= 1;
                    this._OngoingWorkerCount = Math.Max(0, this._OngoingWorkerCount);
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
            finally
            {
                this._SyncObj.ReleaseMutex();
            }
        }

        private void RemoveJobFromMemory(Guid ticketID)
        {
            this._Jobs.Remove(ticketID);
            this._JobWorkMaps.Remove(ticketID);
            foreach (Guid componentID in this._ComponentJobMaps.Keys)
            {
                Guid jobID = this._ComponentJobMaps[componentID];
                if (jobID == ticketID)
                {
                    this._ComponentJobMaps.Remove(componentID);
                }
            }
        }
        #endregion

        #region data pipe
        /// <summary>
        /// Limitation:
        /// Instruction is instantiated from one type
        /// Unable to handle transformation, only assignment and type has to agree with each other
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        private InstructionBase ComputeInstructionForJob(Job job)
        {
            // 1: get run id using job id (from workerinstanceid to workflowinstanceid)
            Run run = WorkflowManager.GetRunByJob(job.JobID);
            // 2: get workflow component by job id 

            // 3: get upstream connectors and components
            WorkflowDesign wfDesign = WorkflowManager.GetWorkflow(run.WorkflowID);
            WorkflowComponentBase wfComponent = wfDesign.Components[job.ComponentID];
            InstructionBase instruction = wfComponent.Instruction;
            List<WorkflowComponentBase> upstreamComponents = new List<WorkflowComponentBase>();
            if (wfDesign.SuccessConnectors != null && wfDesign.SuccessConnectors.Count > 0)
            {
                foreach (Guid fromID in wfDesign.SuccessConnectors.Keys)
                {
                    if (wfDesign.SuccessConnectors[fromID].Contains(job.ComponentID))
                    {
                        WorkflowComponentBase upstreamComponent = wfDesign.Components[fromID];
                        if (!upstreamComponents.Contains(upstreamComponent))
                            upstreamComponents.Add(upstreamComponent);
                    }
                }
            }
            if (wfDesign.FailureConnectors != null && wfDesign.FailureConnectors.Count > 0)
            {
                foreach (Guid fromID in wfDesign.FailureConnectors.Keys)
                {
                    if (wfDesign.FailureConnectors[fromID].Contains(job.ComponentID))
                    {
                        WorkflowComponentBase upstreamComponent = wfDesign.Components[fromID];
                        if (!upstreamComponents.Contains(upstreamComponent))
                            upstreamComponents.Add(upstreamComponent);
                    }
                }
            }
           
            // 4: get field mappers
            if (instruction != null)
            {
                if (upstreamComponents.Count > 0)
                {
                    foreach (WorkflowComponentBase upstreamComponent in upstreamComponents)
                    {
                        if(wfDesign.DataPipeConverters!=null && wfDesign.DataPipeConverters.ContainsKey(upstreamComponent.ComponentID) && 
                            wfDesign.DataPipeConverters[upstreamComponent.ComponentID].ContainsKey(wfComponent.ComponentID))
                        {
                            ResultBase jobOutput = WorkflowManager.GetJobOutputByComponent(upstreamComponent.ComponentID, run.RunID);
                            List<CodeFieldMapper> mappers =
                                wfDesign.DataPipeConverters[upstreamComponent.ComponentID][wfComponent.ComponentID];
                            WorkflowManager.UpdateJobInstruction(ref instruction, jobOutput, mappers.ToArray());
                        }
                    }
                }
                return instruction;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region notification
        private void LogErrorMessage(string msg)
        {
            this._Log.WriteEntry(msg, EventLogEntryType.Error);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        private void LogInfo(string msg)
        {
            this._Log.WriteEntry(msg, EventLogEntryType.Information);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="subj"></param>
        /// <param name="source"></param>
        private void SendNotification(string msg, string subj, string source)
        {
            try
            {
                string toEmailAddrs = string.Empty;
                if (this.ExistInConfigFile("SendToEmailAddress"))
                    toEmailAddrs = ConfigurationManager.AppSettings.Get("SendToEmailAddress");
                if (!string.IsNullOrEmpty(toEmailAddrs))
                {
                    string[] sendToEmailAddr =
                        toEmailAddrs.Split(new char[] {';', ','});
                    bool requireAuthentication =
                        bool.Parse(ConfigurationManager.AppSettings.Get("RequireAuthentication"));
                    bool enableSSL = bool.Parse(ConfigurationManager.AppSettings.Get("EnableSSL"));
                    string smtpServerName = ConfigurationManager.AppSettings.Get("SmtpServer");
                    MailMessage mail = new MailMessage();
                    mail.From = new MailAddress("WorkflowService@" + Environment.UserDomainName + ".com");
                    mail.Subject = "<b>" + subj + "</b>";
                    foreach (string toAddr in sendToEmailAddr)
                    {
                        mail.To.Add(new MailAddress(toAddr));
                    }
                    StringBuilder buffer = new StringBuilder();
                    buffer.Append("<b>" + source + "</b>");
                    buffer.Append("<hr/>");
                    buffer.Append(msg);
                    mail.Body = buffer.ToString();
                    mail.IsBodyHtml = true;
                    mail.Priority = MailPriority.High;
                    SmtpClient mailClient = new SmtpClient(smtpServerName);
                    if (requireAuthentication)
                    {
                        string userName = ConfigurationManager.AppSettings.Get("EmailUserName");
                        string password = ConfigurationManager.AppSettings.Get("EmailPassword");
                        mailClient.Credentials = new NetworkCredential(userName, password);
                        mailClient.EnableSsl = enableSSL;
                    }
                    mailClient.Send(mail);
                }
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
        }

        private void SendNotification(
            string smtpServer, bool enableSSL,
            bool requireAuthentication, string userID, string password,
            string fromAddr,
            string[] toAddresses, string[] ccAddresses,
            string subj, string body)
        {
            try
            {
                MailMessage message = new MailMessage();
                message.From = new MailAddress(fromAddr);
                foreach (string toAddr in toAddresses)
                {
                    message.To.Add(new MailAddress(toAddr));
                }
                if (ccAddresses != null && ccAddresses.Length > 0)
                {
                    foreach (string ccAddr in ccAddresses)
                    {
                        message.CC.Add(new MailAddress(ccAddr));
                    }
                }
                message.Subject = subj;
                message.Body = body;
                message.Priority = MailPriority.High;
                message.IsBodyHtml = true;
                SmtpClient smtp = new SmtpClient(smtpServer);
                if (requireAuthentication)
                {
                    smtp.Credentials = new NetworkCredential(userID, password);
                }
                if (enableSSL)
                {
                    smtp.EnableSsl = true;
                }
                smtp.SendAsync(message, null);
            }
            catch (Exception ex)
            {
                this.LogErrorMessage(ex.ToString());
                this.LogErrorMessage(ex.StackTrace);
            }
        }
        #endregion
    }
}
